#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define MAXCHAR 1024  //最多存储1024个字符
#define MAXSTR 64     //最多拆分成64个选项字符串
char Scommand[MAXCHAR]={0};//存储命令行指令
char* argv[MAXSTR]={0};    //存储命令行指令拆分成的选项


#define NONE_REDIR   0   //不进行重定向
#define INPUT_REDIR  1   //输入重定向
#define OUTPUT_REDIR 2   //输出重定向
#define APPEND_REDIR 3   //追加重定向
#define SPACEPASS(start) do{\
    while(*start==' ') start++;\
}while(0)

int exit_sign=0;//记录最近一次退出的子进程的退出信号
int exit_code=0;//记录最近一次退出的子进程的退出码
int redirType=NONE_REDIR;//重定向类型，默认不进行重定向
char* redirFile=NULL;    //重定向文件名


/*
 * 当前程序仅是简易版bash，很多功能不完善，例如命令行输入时无法删除已输入内容，无法执行自定义可执行程序以及部分指令操作
 * 将来再慢慢改进
 */


void commandcheck(char* command)
{
  assert(command);//命令不能为NULL
  char* start=command;
  char* end=command+strlen(command);
  while(start<end)
  {
    if(*start=='>')//输出/追加重定向
    {
      *start=0;//进行截断，不影响前半部分的指令
      start++;
      if(*start=='>')//追加重定向
      {
        redirType=APPEND_REDIR;//记录重定向类型
        start++;
      }
      else//输出重定向
      redirType=OUTPUT_REDIR;
      SPACEPASS(start);//跳过空格
      redirFile=start;//记录重定向的目标文件
    }
    else if(*start=='<')//输入重定向
    {
      *start=0;//截断，不影响前半部分指令
      start++;
      redirType=INPUT_REDIR;//记录重定向类型
      SPACEPASS(start);//跳过空格
      redirFile=start;//记录重定向的目标文件
    }
    else//不是重定向符合
    {
      start++;
    }
  }

}

 int main()
{
  while(1)//永不停歇
  {
      //初始化重定向信息
      redirType=NONE_REDIR;
      redirFile=NULL;

      //打印命令行提示符
      printf("[%s@%s %s]$ ",getenv("USER"),getenv("HOSTNAME"),"MY_OWN_SHELL");
      fflush(stdout);

      //读取命令行内容
      fgets(Scommand,MAXCHAR-1,stdin);//给Scommand数组预留一个空间放\0
      Scommand[strlen(Scommand)-1]=0;//将最后一个有效字符(\n)置为\0

      //先获取重定向信息,并在重定向符号处对字符串进行切割
      commandcheck(Scommand);

      //拆分读取到的字符串，"ls -a -l" -> "ls" "-a" "-l"
      argv[0]=strtok(Scommand," ");//以空格为分隔符先截取第一个选项，即程序名
      int i=1;
      while(argv[i++]=strtok(NULL," "))//拆分结束停止
      {
        if(i==MAXSTR)//字符串数字全部填满非空字符串，意味着指令太多了
          break;
      }
      if(i==MAXSTR && argv[i-1]!=NULL)//选项太多
      {
          printf("too much orders\n");
          continue;
      }


      //cd指令
      if(argv[0]!=NULL && strcmp(argv[0],"cd")==0)
      {
        if(argv[1]!=NULL)
        {
          chdir(argv[1]);//修改shell的工作目录
        }
        continue;
      }
      

      //echo指令
      if(argv[0]!=NULL && strcmp(argv[0],"echo")==0)
      {
        int j=1;
        for(j=1;argv[j];j++)
        {
          if(strcmp(argv[j],"$?")==0)
            printf("%d %d ",exit_sign,exit_code);
          else
            printf("%s ",argv[j]);
        }
        printf("\n");
        continue;
      }
      
      //argv[i]=NULL;//保险起见后面垫一个NULL
      /*测试字符串拆分的效果
      for(i=0;argv[i];i++)
        printf("%s\n",argv[i]);
        */

      //创建子进程，将子进程替换执行其他程序
      int id=fork();
      assert(id!=-1);//子进程创建失败报警
      if(id==0)//子进程
      {
        //先对子进程进行重定向操作
        switch(redirType)
        {
          case NONE_REDIR:
            break;
          case INPUT_REDIR:
            {
              int fd=open(redirFile,O_RDONLY);
              if(fd<0)
              {
                perror("open failed");
                exit(errno);
              }
              //打开文件成功
              dup2(fd,0);
            }
            break;
          case OUTPUT_REDIR:
          case APPEND_REDIR:
            {
              int flags=O_WRONLY | O_CREAT;
              if(redirType==OUTPUT_REDIR)
                flags |=O_TRUNC;
              else
                flags |=O_APPEND;
              int fd=open(redirFile,flags,0666);
              if(fd<0)
              {
                perror("open failed");
                exit(errno);
              }
              //打开文件成功
              dup2(fd,1);
            }
            break;
          default:
            {
              perror("重定向出错");
              exit(errno);
            }
            break;
        }
        //子进程
        execvp(argv[0],argv);//此接口只适用于指令程序，替换执行自己写的程序会找不到文件
        //发生了进程替换还能走到这里一定是替换失败。一定是程序没找到
        printf("failed assess: no such file or directory\n");
        exit(1);
      }
      int status=0;
      //父进程等待回收子进程，在子进程执行期间，父进程处于阻塞状态(模拟前台进程执行时命令行提示符失效)
      waitpid(id,&status,0);
      exit_sign=status & 0x7f;//记录子进程的退出信号
      exit_code=(status>>8) & 0xff;//记录子进程的退出码
      
  }
  return 0;
}
